package moltencore.game.bubble;

import spark.math.Vector2;

import java.awt.*;
import java.util.LinkedList;
import java.util.Vector;
import java.util.Collection;

import moltencore.game.collision.ICollidable;
import org.apache.log4j.Logger;

public class Bubble extends AbstractBubble {
	private static Logger logger = Logger.getLogger(Bubble.class);

	//game mechanics information
	public static final int LEFT_BOT = 0;
	public static final int RIGHT_BOT = 1;
	public static final int LEFT = 2;
	public static final int RIGHT = 3;
	public static final int LEFT_TOP = 4;
	public static final int RIGHT_TOP = 5;
	private static final int[] INVERSE_NEIGHBORS = {RIGHT_TOP, LEFT_TOP, RIGHT, LEFT, RIGHT_BOT, LEFT_BOT};


	private Bubble[] neighbors = new Bubble[6];
	private boolean attachedToWorld = false;
	private Point gridPosition = new Point();
	private boolean pushCounted = true;

	public Bubble(Type type, short bubbleId) {
		super(type, bubbleId);
	}

	public Bubble(Bubble other) {
		super(other);
		this.gridPosition = new Point(other.gridPosition);
	}

	public BubbleChain getBubbleChain() {
		BubbleChain chain = new BubbleChain();
		getBubbleChain(chain, this.getType(), new Vector<Bubble>());
		if(type == Type.Rainbow) {
			Vector<Bubble> surroundingBubbles = getSurroundingBubbles(chain.getChainBubblesByCriticalMass(0));
			Vector<Type> typesContained = new Vector<Type>();
			for(Bubble currentBubble : surroundingBubbles) {
				if(!currentBubble.getType().isOrdinary()) {
					continue;
				}
				if(typesContained.contains(currentBubble.getType())) {
					continue;
				}
				currentBubble.getBubbleChain(chain, currentBubble.getType(), new Vector<Bubble>());
				typesContained.add(currentBubble.getType());
			}
		}
		return chain;
	}

	private void getBubbleChain(BubbleChain chain, Type type, Vector<Bubble> checked) {
		if(checked.contains(this))
			return;
		checked.add(this);
		if(!chain.nextToChain(this) && !chain.isEmpty()) {
			return;
		}
		if(this.type == type || this.type == Type.Rainbow) {
			chain.add(this, type);
		}
		else
			return;
		for(Bubble anAttached : neighbors) {
			if(anAttached != null) {
				anAttached.getBubbleChain(chain, type, checked);
			}
		}
	}

	public static Vector<Bubble> getSurroundingBubbles(Collection<Bubble> centerBubbles) {
		Vector<Bubble> surroundingBubbles = new Vector<Bubble>();
		Vector<Bubble> centerBubbleCopy = new Vector<Bubble>(centerBubbles);
		for(Bubble currentBubble : centerBubbles) {
			for(Bubble surroundingBubble : currentBubble.neighbors) {
				if(surroundingBubble != null &&
				   !surroundingBubbles.contains(surroundingBubble)) {
					surroundingBubbles.add(surroundingBubble);
				}

			}
		}
		surroundingBubbles.removeAll(centerBubbleCopy);
		return surroundingBubbles;
	}

	/**
	 * @return true if the bubble is not attached to the world (directly or over it's neighbours), false otherwise
	 */
	public boolean isHangingFree() {
		Vector<Bubble> checked = new Vector<Bubble>();
		return isHangingFree(checked);
	}

	private boolean isHangingFree(Vector<Bubble> checked) {
		if(checked.contains(this)) {
			return true;
		}
		else if(isAttachedToWorld()) {
			return false;
		}
		else {
			checked.add(this);
			for(Bubble anAttached : neighbors) {
				if(anAttached != null &&
				   !anAttached.isHangingFree(checked)) {
					return false;
				}
			}
		}
		return true;
	}

	public void detach() {
		for(Bubble currentBubble : neighbors) {
			if(currentBubble != null) {
				currentBubble.removeNeighbor(this);
			}
		}
		removeAllNeighbors();
		setAttachedToWorld(false);
	}

	private void removeNeighbor(Bubble toRemove) {
		for(int i = 0; i < neighbors.length; i++) {
			if(toRemove.equals(neighbors[i])) {
				neighbors[i] = null;
				return;
			}
		}
	}

	private void removeAllNeighbors() {
		for(int i = 0; i < neighbors.length; i++) {
			neighbors[i] = null;
		}
	}

	public Vector<Bubble> gatherBubbleMass() {
		Vector<Bubble> mass = new Vector<Bubble>();
		gatherBubbleMass(mass);
		return mass;
	}

	private void gatherBubbleMass(Vector<Bubble> mass) {
		if(mass.contains(this))
			return;
		mass.add(this);
		for(Bubble anAttached : neighbors) {
			if(anAttached != null) {
				anAttached.gatherBubbleMass(mass);
			}
		}
	}

	public void attach(Bubble bubble, int neighborPosition) {
		neighbors[neighborPosition] = bubble;
		bubble.neighbors[INVERSE_NEIGHBORS[neighborPosition]] = this;
	}

	public Point getGridPosition() {
		return gridPosition;
	}

	public void setAttachedToWorld(boolean attachedToWorld) {
		this.attachedToWorld = attachedToWorld;
	}

	public boolean isAttachedToWorld() {
		return attachedToWorld;
	}

	public Bubble[] getNeighbors() {
		return neighbors;
	}

	public boolean isPushCounted() {
		return pushCounted;
	}

	public void setPushCounted(boolean pushCounted) {
		this.pushCounted = pushCounted;
	}
}
